package org.lex.game.sudoku.model.technique;

import java.util.HashSet;
import java.util.Set;

import ms.sudoku.util.IntSet;

import org.lex.game.sudoku.model.Point;
import org.lex.game.sudoku.model.SudokuModel;
import org.lex.game.sudoku.model.visitor.ModelVisitors;
import org.lex.game.sudoku.model.visitor.NumberPositionVisitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestTech {
	private final Logger log = LoggerFactory.getLogger(TestTech.class);

	public IntSet test(SudokuModel model, int row, int col, IntSet hints) {
		if (hints.isEmpty()) {
			return hints;
		} else if (1 == hints.size()) {
			return hints;
		}
		int boxSize = model.getBoxSize();
		int gridSize = model.getGridSize();
		Point blockBegin = null;
		Point blockEnd = null;
		for (int hint : hints) {
			// by row
			{
				int rowStart = ModelVisitors.getBoxStart(model, row, col).x;
				blockBegin = new Point(rowStart, 0);
				blockEnd = new Point(rowStart + boxSize, gridSize);

				Set<Point> positions = ModelVisitors.visit(model, new NumberPositionVisitor(hint), blockBegin.x,
						blockBegin.y, blockEnd.x, blockEnd.y);
				if ((boxSize - 1) == positions.size()) {
					int startBoxIndex = ModelVisitors.getBoxIndex(model, blockBegin.x, blockBegin.y);
					int targetRow = -1;
					int targetColBegin = -1;
					// find row to test
					findTestRowLoop: for (int boxIndex = startBoxIndex; boxIndex < startBoxIndex + boxSize; boxIndex++) {
						if (false == this.containsAnyPoint(model, boxIndex, positions)) {
							Point boxStart = ModelVisitors.getBoxStart(model, boxIndex);
							targetColBegin = boxStart.y;
							IntSet rowSet = new IntSet(model.getGridSize());
							for (int boxRow = boxStart.x; boxRow < boxStart.x + boxSize; boxRow++) {
								rowSet.add(boxRow);
							}
							for (Point p : positions) {
								rowSet.remove(p.x);
							}
							if (1 != rowSet.size()) {
								log.error("ERROR: hints=" + hints + ", currentHint=" + hint + ", positions="
										+ positions + " --> rows=" + rowSet);
								return hints;
							}
							targetRow = rowSet.pop();
							break findTestRowLoop;
						}
					}
					// test if hint can fill into target row
					Set<Point> validCells = new HashSet<>();
					for (int targetCol = targetColBegin; targetCol < targetColBegin + boxSize; targetCol++) {
						if (model.checkCanFillWith(targetRow, targetCol, hint)) {
							validCells.add(new Point(targetRow, targetCol));
						}
					}
					if (1 == validCells.size()) {
						Point p = validCells.iterator().next();
						if (p.x == row && p.y == col) {
							hints.clear();
							hints.add(hint);
							return hints;
						}
					}
				}
			}
			// ==============
			// by col
			int colStart = ModelVisitors.getBoxStart(model, row, col).y;
			blockBegin = new Point(0, colStart);
			blockEnd = new Point(gridSize, colStart + boxSize);

			Set<Point> positions = ModelVisitors.visit(model, new NumberPositionVisitor(hint), blockBegin.x,
					blockBegin.y, blockEnd.x, blockEnd.y);
			if ((boxSize - 1) == positions.size()) {
				int startBoxIndex = ModelVisitors.getBoxIndex(model, blockBegin.x, blockBegin.y);
				int targetRowBegin = -1;
				int targetCol = -1;
				// find col to test
				findTestColLoop: for (int boxIndex = startBoxIndex; boxIndex < gridSize; boxIndex += boxSize) {
					if (false == this.containsAnyPoint(model, boxIndex, positions)) {
						Point boxStart = ModelVisitors.getBoxStart(model, boxIndex);
						targetRowBegin = boxStart.x;
						IntSet colSet = new IntSet(model.getGridSize());
						for (int boxCol = boxStart.y; boxCol < boxStart.y + boxSize; boxCol++) {
							colSet.add(boxCol);
						}
						for (Point p : positions) {
							colSet.remove(p.y);
						}
						if (1 != colSet.size()) {
							log.error("ERROR: hints=" + hints + ", currentHint=" + hint + ", positions=" + positions
									+ " --> cols=" + colSet);
							return hints;
						}
						targetCol = colSet.pop();
						break findTestColLoop;
					}
				}
				// test if hint can fill into target col
				Set<Point> validCells = new HashSet<>();
				for (int targetRow = targetRowBegin; targetRow < targetRowBegin + boxSize; targetRow++) {
					if (model.checkCanFillWith(targetRow, targetCol, hint)) {
						validCells.add(new Point(targetRow, targetCol));
					}
				}
				if (1 == validCells.size()) {
					Point p = validCells.iterator().next();
					if (p.x == row && p.y == col) {
						hints.clear();
						hints.add(hint);
						return hints;
					}
				}
			}
		}
		return hints;
	}

	private boolean containsAnyPoint(SudokuModel model, int boxIndex, Set<Point> points) {
		for (Point p : points) {
			if (boxIndex == ModelVisitors.getBoxIndex(model, p.x, p.y)) {
				return true;
			}
		}
		return false;
	}
}
